classdef RomerRouter < Router
    
    properties 
        number_of_duplicate_measurements = 0;
        total_sum_of_packet_duplicates = 0;
    end
    
    methods
        
        function obj = RomerRouter(topology)
            obj = obj@Router();
            if nargin > 0
                obj.topology = topology;
            end
            obj.number_of_packets_discarded_because_of_threshold = 0;
        end
        
        function update_avg_number_of_duplicates(obj, num)
            obj.number_of_duplicate_measurements = obj.number_of_duplicate_measurements+1;
            obj.total_sum_of_packet_duplicates = obj.total_sum_of_packet_duplicates+num;
            obj.avg_number_of_packet_duplicates = obj.total_sum_of_packet_duplicates / obj.number_of_duplicate_measurements;
        end
        
        %PR - can this be done once for all packets deposited to the node??
        %create copies of the packets according to ROMER algo.
        function packets_for_transmission = get_packets_for_transmission(obj, transmitting_node, packets_vec)
            
            utils = Utils.instance();
            utils.trace(sprintf('router [node id %d]: preparing %d packets\n',transmitting_node.id,length(packets_vec)));
            
            node_neighbours = obj.topology.get_neighbours(transmitting_node.id);
            
            link_rates_vector = [];
            for i=1:length(node_neighbours)
                curr_link_rate = transmitting_node.node_adapter.link_rate_generator.get_link_rate_in_Kbps(i);
                link_rates_vector = [link_rates_vector curr_link_rate];
            end
            
            should_romer_forward_packets_vec(length(node_neighbours)) = false;
            for i=1:length(node_neighbours)
                should_romer_forward_packets_vec(i) =  obj.should_romer_forward_packet(i, link_rates_vector);
            end
            
            %trace
            utils.trace(sprintf('router [node id %d]: routing table (id,should_forward) [',transmitting_node.id));
            for i=1:length(node_neighbours)
                utils.trace_no_tick(sprintf('(%d,%d) ,',node_neighbours(i).id,should_romer_forward_packets_vec(i)));
            end
            utils.trace_no_tick(sprintf(' ] \n'));
            %trace
            
             max_num_of_packets_for_transmission = length(packets_vec)*length(node_neighbours);
             packets_for_transmission(1,max_num_of_packets_for_transmission) = MultiPacket();
             num_of_packets_for_transmission = 0;
             
             for i=1:length(packets_vec)
                 curr_multi_packet = packets_vec(i);
                 curr_packet = curr_multi_packet.packet;
                 num_of_packet_duplications = 0;
                 if ~curr_packet.should_be_discarded
                     for j=1:length(node_neighbours)
                         if should_romer_forward_packets_vec(j)==true
                             %check if packet is going upstream
                             new_multi_packet = curr_multi_packet.duplicate_multi_packet();
                             new_multi_packet.packet.current_node = transmitting_node;
                             new_multi_packet.packet.next_node = node_neighbours(j);
                             new_multi_packet.channel_index = j;
                             packets_for_transmission(num_of_packets_for_transmission+1) = new_multi_packet;
                             num_of_packets_for_transmission = num_of_packets_for_transmission+1;
                             num_of_packet_duplications = num_of_packet_duplications+1;
                             utils.trace(sprintf('router [node id %d]: duplicated packet id %d to node %d \n',transmitting_node.id,curr_multi_packet.id,node_neighbours(j).id));
                         end
                     end
                     obj.update_avg_number_of_duplicates(num_of_packet_duplications);
                 end
             end
             packets_for_transmission = packets_for_transmission(1:num_of_packets_for_transmission);
        end
        
        %the function decides whether the packet should be forwarded
        function ret = should_romer_forward_packet(obj, link_index, link_rates_vector)
            ret = false;
            params = ParamsSingleton.instance();
            rand_value = rand;
            
            if params.should_use_constant_forward_probability
                pl = params.constant_forward_probability;
                assert(pl<=1);
                ret = (pl > rand_value);
                return;
            end
            
            %calc the forward probability (according to ROMER, see section
            %III.B)
            max_forward_copies = ParamsSingleton.instance().max_number_of_forwarding_copies;
            total_rate_sum = sum(link_rates_vector);
            if ~params.should_limit_number_of_forward_copies
                Rmax = max(link_rates_vector);
                pl = link_rates_vector(link_index)/Rmax;
            else
                pl = (link_rates_vector(link_index)/total_rate_sum)*max_forward_copies;
            end
           % MessagesPrinter.instance().write_message('forward probability is %f\r\n',pl);
            assert(pl<=1);
            ret = (pl > rand_value);
        end
        
        
        function route_packets_to_nodes(obj,packets_to_route)
            utils = Utils.instance();
            utils.trace(sprintf('router is routing %d packets\n',length(packets_to_route)));
            
            params = ParamsSingleton.instance();
            
            global_depositor = GlobalPacketDepositor.instance();
            
            for i = 1:length(packets_to_route)
                multi_packet = packets_to_route(i);
                if multi_packet.num_of_packets ==0
                    assert(false);
                    continue;
                end
                
                utils.trace(sprintf('router is handling packet id %d\n',multi_packet.id));
                
                packet = multi_packet.packet;
                current_node = packet.current_node;
                current_neighbour = packet.next_node;
                alpha = packet.romer_alpha;
                c_source = packet.romer_c_source;
                
                min_cost_to_dest_from_current_node = current_node.distance_vector(packet.dest_node.id);
                min_cost_to_dest_from_neighbour_node = current_neighbour.distance_vector(packet.dest_node.id);
                cA = min_cost_to_dest_from_neighbour_node;
                cA_to_Csource = (cA / c_source);
                power = params.ROMER_threshold_power;
                threshold = cA_to_Csource^power;
                
                total_credit = (1 + alpha) * c_source;
                consumed_cost = total_credit - packet.remaining_credit;
                delta_min_cost = min_cost_to_dest_from_current_node - min_cost_to_dest_from_neighbour_node;
                % CR - this check should happen before transmitting the
                % packet - stop packets from going upstream
                delta_cost = delta_min_cost+2*alpha*delta_min_cost*cA_to_Csource;
                new_consumed_cost = consumed_cost + delta_cost;
                ratio_to_compare = max(0, (total_credit - (new_consumed_cost + min_cost_to_dest_from_neighbour_node)) / (alpha * c_source));
                
                if delta_min_cost < 0
                    utils.trace(sprintf('router stopped multi packet id %d from going %d => %d (upstream)\n', multi_packet.id, packet.current_node.id,packet.next_node.id));
                    continue;
                end
                
                utils.trace_no_tick('\n');
                utils.trace(sprintf('=========== ROMER data============\n'));
                utils.trace(sprintf('|| alpha: %f\n',alpha));
                utils.trace(sprintf('|| packet c_source: %s\n',num2str(c_source)));
                utils.trace(sprintf('|| packet c_A: %s\n',num2str(min_cost_to_dest_from_neighbour_node)));
                utils.trace(sprintf('|| packet total credit: %s\n',num2str(total_credit)));
                utils.trace(sprintf('|| cost consumed so far: %s\n',num2str(consumed_cost)));
                utils.trace(sprintf('|| delat cost: %s\n',num2str(delta_cost)));
                utils.trace(sprintf('|| new cost consumed: %s\n',num2str(new_consumed_cost)));
                utils.trace(sprintf('|| th: %f\n',threshold));
                utils.trace(sprintf('|| calculated ratio: %f\n',ratio_to_compare));
                utils.trace(sprintf('=========== ROMER data============\n\n'));
                
                if (ratio_to_compare >= threshold)
                    % deposit the new packet to the current neighbour
                    utils.trace(sprintf('router forwarding packet id %d (%d => %d)\n', multi_packet.id, current_node.id,current_neighbour.id));
                    
                    remaining_credit =max(0,total_credit - new_consumed_cost);
                    new_romer_packet = RomerPacket(packet.id,...
                                                   packet.size_in_bytes,...
                                                   packet.src_node,...
                                                   packet.dest_node,...
                                                   packet.route,...
                                                   packet.romer_alpha,...
                                                   remaining_credit,...
                                                   packet.romer_c_source);                     
                     new_multi_packet = MultiPacket(multi_packet.id, new_romer_packet, multi_packet.packet_id_range_min, multi_packet.packet_id_range_max);
                    global_depositor.deposit_packet_to_node(current_neighbour.id, new_multi_packet);
                else
                    utils.trace(sprintf('router stopped forwarding multi packet id %d (%d => %d) threshold not met!\n', multi_packet.id, current_node.id,current_neighbour.id));
                    obj.number_of_packets_discarded_because_of_threshold = obj.number_of_packets_discarded_because_of_threshold+multi_packet.num_of_packets;
                end
            end
        end
    end
end